#include "list.h"
#include <stdio.h>

/**
 * @brief Create a list from heap
 * @retval list_t *list pointer
 */
list_t *listCreate(void)
{
    list_t *plist = _listMalloc(sizeof(list_t));
    if (plist == NULL)
        return NULL;
    plist->head = NULL;
    plist->tail = NULL;
    plist->len = 0;
    return plist;
}

/**
 * @brief Create a list static
 * @retval list_t *list pointer
 * @warning the static list can't use the listDestroy !!!
 */
list_t *listStaticCreate(list_t *plist)
{
    if (plist == NULL)
        return NULL;
    plist->head = NULL;
    plist->tail = NULL;
    plist->len = 0;
    return plist;
}

/**
 * @brief Create a node from heap
 * @param data push data to node
 * @retval node_t* new node pointer
 */
node_t *listCreateNode(void *data)
{
    node_t *pnode;
    pnode = _listMalloc(sizeof(node_t));
    if (pnode == NULL)
        return pnode;
    pnode->data = data;
    pnode->next = NULL;
    pnode->prior = NULL;
    return pnode;
}

/**
 * @brief Create a node with static node
 * @param data push data to node
 * @retval node_t* new node pointer
 */
node_t *listStaticCreateNode(node_t *pnode, void *data)
{
    pnode->data = data;
    pnode->next = NULL;
    pnode->prior = NULL;
    return pnode;
}

/**
 * @brief Check list has next node
 * @param node current node
 * @retval node_t* 1:ture / 0:false
 */
_LIST_USE_INLINE node_t *NodeGetNext(node_t *node)
{
    _listAssert(node != NULL);
    return (node->next);
}

/**
 * @brief Move to next node
 * @param node current node
 * @retval node_t* Next node or NULL
 */
_LIST_USE_INLINE node_t *NodeToNext(node_t **node)
{
    _listAssert(node != NULL);
    return (*node = (*node)->next);
}

/**
 * @brief Check list has prior node
 * @param node current node
 * @retval node_t* 1:ture / 0:false
 */
_LIST_USE_INLINE node_t *NodeGetPrior(node_t *node)
{
    _listAssert(node != NULL);
    return (node->prior);
}

/**
 * @brief Move to prior node
 * @param node current node
 * @retval node_t* Prior node or NULL
 */
_LIST_USE_INLINE node_t *NodeToPrior(node_t** node)
{
    _listAssert(node != NULL);
    return (*node = (*node)->prior);
}

/**
 * @brief Write node data 
 * @param node current node
 */
_LIST_USE_INLINE void NodeWrite(node_t *node, void *data)
{
    _listAssert(node != NULL);
    node = node->data = data;
}

/**
 * @brief Read node data 
 * @param node current node
 */
_LIST_USE_INLINE void *NodeRead(node_t *node)
{
    _listAssert(node != NULL);
    return node->data;
}

/**
 * @brief Get the node with the specified index
 * @param root List root
 * @param index  Node index [0,listLength-1]
 * @retval Position node list_t*
 */
node_t *listGetNode(list_t *root, uint32_t index)
{
    _listAssert(root != NULL);
    _listAssert(index < root->len);

    node_t *pnode;
    if (index < (root->len >> 1))
    {
        pnode = root->head;
        while (index)
        {
            index--;
            _listAssert(pnode != NULL);
            pnode = pnode->next;
        }
    }
    else
    {
        pnode = root->tail;
        index = root->len - 1 - index;
        while (index)
        {
            index--;
            _listAssert(pnode != NULL);
            pnode = pnode->prior;
        }
    }
    return pnode;
}

/**
 * @brief Get list len
 * @param root List root
 * @retval uint32_t List len
 */
_LIST_USE_INLINE uint32_t listGetLen(list_t *root)
{
    _listAssert(root != NULL);
    return root->len;
}

/**
 * @brief Get list head node
 * @param root List root
 * @retval node_t* head Node
 */
_LIST_USE_INLINE node_t *listGetHead(list_t *root)
{
    _listAssert(root != NULL);
    return root->head;
}

/**
 * @brief Get list tail node
 * @param root List root
 * @retval node_t* tail Node
 */
_LIST_USE_INLINE node_t *listGetTail(list_t *root)
{
    _listAssert(root != NULL);
    return root->tail;
}

/**
 * @brief Search data from list
 * @param root List root
 * @param data
 * @retval node_t*
 */
node_t *listSearchData(list_t *root, void *data)
{
    _listAssert(root != NULL);

    node_t *pnode = root->head;
    while (pnode)
    {
        if (pnode->data == data)
            return pnode;
        pnode = pnode->next;
    }
    return NULL;
}

/**
 * @brief Search node from list
 * @param root List root
 * @param node
 * @retval [0:len-1]:node pos / [len]:not find
 */
uint32_t listSearchNode(list_t *root, node_t *pnode)
{
    _listAssert(root != NULL);
    _listAssert(pnode != NULL);

    node_t *p = root->head;
    uint32_t pos;
    for (pos = 0; pos < root->len; pos++)
    {
        if (p == pnode)
            break;
        p = p->next;
    }
    return pos;
}

/**
 * @brief List write node data at index
 * @param root List root
 * @param index 
 * @param data 
 * @retval _LIST_USE_INLINE 
 */
_LIST_USE_INLINE void listWrite(list_t *root, uint32_t index, void *data)
{
    _listAssert(root != NULL);
    _listAssert(index < root->len);

    listGetNode(root, index)->data = data;
}

/**
 * @brief List read node data at index
 * @param root 
 * @param index 
 * @retval void* 
 */
_LIST_USE_INLINE void *listRead(list_t *root, uint32_t index)
{
    _listAssert(root != NULL);
    _listAssert(index < root->len);

    return listGetNode(root, index)->data;
}

/**
 * @brief Insert node at pos next
 * @param root List root
 * @param node Need to insert node
 * @param pos  Insert Position [0,listLength]
 * @retval uint32_t listLength
 * @warning The node to be inserted must be an unused node !!!
 */
uint32_t listInsertNodeAt(list_t *root, node_t *node, uint32_t pos)
{
    _listAssert(root != NULL);
    _listAssert(node != NULL);
    _listAssert(node->next == NULL && node->prior == NULL);
    _listAssert(pos < root->len+1);

    node_t *pnode;
    if (root->len == 0)
    {
        root->head = node;
        root->tail = node;
    }
    else if(pos == 0)
    {
        /* list: [0] [1] ... [len - 1]  */
        /*      ^[insert into the head] */
        node->next = root->head;
        node->next->prior = node;
        root->head = node;
    }
    else if (pos == root->len)
    {
        /* list: [0] ... [len - 2] [len - 1]                        */
        /*                                  ^[insert into the tail] */
        node->prior = root->tail;
        node->prior->next = node;
        root->tail = node;
    }
    else
    {
        /* list: [0] ... [len - 1]              */
        /*            ^[insert into the middle] */
        pnode = listGetNode(root, pos-1);
        node->next = pnode->next;
        node->next->prior = node;
        node->prior = pnode;
        pnode->next = node;
    }
    return ++root->len;
}

/**
 * @brief list add node to pos next
 * @param root List root
 * @param data
 * @param pos [0,listLenth]
 * @retval uint32_t listLength
 */
uint32_t listPush(list_t *root, uint32_t pos, void *data)
{
    _listAssert(root != NULL);

    node_t *pnode;
    pnode = _listMalloc(sizeof(node_t));
    if (pnode == NULL)
        return 0;
    pnode->data = data;
    pnode->next = NULL;
    pnode->prior = NULL;
    return listInsertNodeAt(root, pnode, pos);
}

/**
 * @brief remove a node from list
 * @param root List root
 * @param pnode node to be removed
 * @warning the node must be in this list !!!
 * @retval uint32_t listLength
 */
uint32_t listRemoveNode(list_t *root, node_t *pnode)
{
    _listAssert(root != NULL);
    _listAssert(pnode != NULL);
    _listAssert(listSearchNode(root, pnode) < root->len);

    if (root->len == 1)
    {
        root->head = NULL;
        root->tail = NULL;
    }
    else
    {
        if (pnode == root->head) // head node
        {
            root->head = pnode->next;
            pnode->next->prior = NULL;
        }
        else if (pnode == root->tail) // tail node
        {
            root->tail = pnode->prior;
            pnode->prior->next = NULL;
        }
        else // the middle node
        {
            pnode->prior->next = pnode->next;
            pnode->next->prior = pnode->prior;
        }
    }
    pnode->next = NULL;
    pnode->prior = NULL;

    return --root->len;
}

/**
 * @brief Remove list node with index
 * @param root List root
 * @param index 
 * @retval node_t* Node that have been removed
 */
_LIST_USE_INLINE node_t *listRemoveNodeAt(list_t *root, uint32_t index)
{
    _listAssert(root != NULL);
    _listAssert(index < root->len);
    
    node_t *pnode = listGetNode(root, index);
    listRemoveNode(root, pnode);
    return pnode;
}

/**
 * @brief delete the node from list and free it
 * @param root List root
 * @param pnode node to be removed
 * @retval uint32_t listLength
 */
_LIST_USE_INLINE uint32_t listDeleteNode(list_t *root, node_t *pnode)
{
    _listAssert(root != NULL);
    _listAssert(pnode != NULL);

    listRemoveNode(root, pnode);
    _listFree(pnode);
    return root->len;    
}


/**
 * @brief Delete the index node and free it
 * @param root List root
 * @param index node index [0,listLength-1]
 * @retval uint32_t listLength
 */
uint32_t listDeleteNodeAt(list_t *root, uint32_t index)
{
    _listAssert(root != NULL);

    node_t *pnode = listGetNode(root, index);
    return listDeleteNode(root, pnode);
}

/**
 * @brief Recycle memory from list
 * @param root List root point
 */
void listDestroy(list_t **root)
{
    _listAssert(root != NULL);

    uint32_t i = (*root)->len;
    node_t *pnode;
    while (i--)
    {
        pnode = (*root)->head;
        (*root)->head = pnode->next;
        _listFree(pnode);
    }
    _listFree(*root);
    *root = NULL;
}

/**
 * @brief popping the index item
 * @param root List root
 * @param index the list node index
 * @retval void* data or NULL
 */
void *listPop(list_t *root, uint32_t index)
{
    _listAssert(root != NULL);

    if (root->len == 0 || index >= root->len)
        return NULL;
    void *pdata = NULL;
    node_t *pnode = listGetNode(root, index);
    pdata = pnode->data;
    listDeleteNode(root, pnode);
    return pdata;
}

/**
 * @brief Convert Array to List
 * @param root List root
 * @param arr array point
 * @param len arr length
 * @retval uint32_t arr len
 */
uint32_t listArrayToList(list_t *root, void *arr, uint32_t sizeofType, uint32_t len)
{
    _listAssert(root != NULL);
    uint32_t i;
    uint8_t *p = (uint8_t*)arr;
    for (i = 0; i < len; i++)
    {
        listPusht(root, &p[i * sizeofType]);
    }
    return i;
}

/**
 * @brief Free memory from data
 * @param node 
 * @param data_free 
 */
_LIST_USE_INLINE void listDataFree(node_t *node, void data_free(void *))
{
    _listAssert(node != NULL);
    _listAssert(data_free != NULL);
    data_free(node->data);
    node->data = NULL;
}

/**
 * @brief Free all data memory
 * @param root List root
 * @param data_free 
 */
void listDatasFree(list_t *root, void data_free(void *))
{
    _listAssert(root != NULL);
    _listAssert(data_free != NULL);

    uint32_t i = root->len;
    node_t *pnode = root->head;
    while (i--)
    {
        data_free(pnode->data);
        pnode->data = NULL;
        pnode = pnode->next;
    }
}

